import argparse
import json
import os
import sys
import typing
import logging
from functools import partial
from multiprocessing import Process
from multiprocessing import Pool
from threading import RLock
from threading import Thread
from concurrent.futures import ThreadPoolExecutor
from GnToolBox.basic_tool import BasicTool
from gn_info_collect import GnInfoCollector


class TargetFileFilterBox:

    @classmethod
    def exe_filter(cls, exe_full_path: str) -> bool:
        cmd = "file {} | grep ELF -wo".format(exe_full_path.strip())
        output = os.popen(cmd).read().strip()
        return len(output) != 0 and ".so" not in exe_full_path

    @classmethod
    def so_filter(cls, so_full_path: str) -> bool:
        cmd = "file {} | grep ELF -wo".format(so_full_path.strip())
        output = os.popen(cmd).read().strip()
        return len(output) != 0 and ".so" in so_full_path

    @classmethod
    def etc_filter(cls, etc_full_path: str) -> bool:
        return (cls.exe_filter(etc_full_path) is False) and (cls.so_filter(etc_full_path) is False)


class FindEvaluatorBox:
    """
    评估怎么才是值和文件相匹配
    """
    @classmethod
    def so_evaluator(cls, unit: dict, file_path: str) -> bool:
        value = unit.get("target_name")
        file_base_name = os.path.split(file_path)[-1].lstrip("lib")
        if ".z.so" in file_base_name:
            file_base_name = file_base_name[:file_base_name.index(".z.so")]
        elif ".so" in file_base_name:
            file_base_name = file_base_name[:file_base_name.index(".so")]
        else:
            return False
        value = value.lstrip("lib")
        return value == file_base_name

    @classmethod
    def exe_evaluator(cls, unit: dict, file_path: str) -> bool:
        value = unit.get("target_name")
        file_base_name = os.path.split(file_path)[-1]
        return value == file_base_name

    @classmethod
    def etc_evaluator(cls, unit: dict, file_path: str) -> bool:
        install_dir = unit.get("install_dir")
        if install_dir is None:
            return False
        file_dir, file_base_name = os.path.split(file_path)  # file_dir不带/
        name_flag = unit.get("source_name") == file_base_name
        path_flag = file_dir.endswith(install_dir.rstrip("/"))
        return name_flag and path_flag


class RomAnalysisTool:
    __rom_info_dict = dict()

    __TARGET_NAME_LITERAL = "target_name"
    __SOURCE_NAME_LITERAL = "source_name"
    __PART_NAME_LITERAL = "part_name"
    __SUBSYSTEM_NAME_LITERAL = "subsystem_name"
    __OTHERS_LITERAL = "others"
    __GN_PATH_LITERAL = "gn_path"
    __SIZE_LITERAL = "size"
    __UNIT_LIST_LITERAL = "target_list"

    __resource_type = ("exe", "so", "etc")

    __rom_info_dict_lock = RLock()

    @classmethod
    def __put(cls, subsystem_name: str, part_name: str, target_unit: dict):
        if subsystem_name is None:
            subsystem_name = cls.__OTHERS_LITERAL
        if part_name is None:
            part_name = cls.__OTHERS_LITERAL
        cls.__rom_info_dict_lock.acquire()
        if cls.__rom_info_dict.get(subsystem_name) is None:  # 如果还没有这个子系统
            cls.__rom_info_dict[subsystem_name] = dict()    # 子系统
            cls.__rom_info_dict.get(subsystem_name)[
                cls.__SIZE_LITERAL] = 0  # 给子系统的size赋初值

        if cls.__rom_info_dict.get(subsystem_name).get(part_name) is None:  # 如果没有该部件
            cls.__rom_info_dict.get(subsystem_name)[
                part_name] = dict()  # 创建这个部件
            cls.__rom_info_dict.get(subsystem_name).get(
                part_name)[cls.__UNIT_LIST_LITERAL] = list()    # 创建部件的unit_list
            cls.__rom_info_dict.get(subsystem_name)[
                part_name][cls.__SIZE_LITERAL] = 0  # 给部件的size赋初值
        cls.__rom_info_dict.get(subsystem_name).get(part_name)[
            cls.__UNIT_LIST_LITERAL].append(target_unit)
        size = int(target_unit.get(cls.__SIZE_LITERAL))
        cls.__rom_info_dict.get(subsystem_name).get(part_name)[
            cls.__SIZE_LITERAL] += size
        cls.__rom_info_dict.get(subsystem_name)[
            cls.__SIZE_LITERAL] += size
        cls.__rom_info_dict[cls.__SIZE_LITERAL] += size
        cls.__rom_info_dict_lock.release()

    @classmethod
    def find_abs_files_of_list(cls, dir_list: list, file_filter=None):
        result_list = list()
        for dl in dir_list:
            result_list.extend(BasicTool.find_abs_files(dl, file_filter))
        return result_list

    @classmethod
    def __analysis_template(cls, preference_key_tuple: tuple, full_path_files: list, gn_info_dict: dict, find_evaluator):
        for ffn in full_path_files:
            base_name = os.path.split(ffn)[-1]
            found_flag = False
            size = os.path.getsize(ffn)
            # def base_name_processor(
            #     x): return x if base_name_processor is None else base_name_processor
            for key in preference_key_tuple:
                info_list = gn_info_dict.get(key)
                for info_dict in info_list:
                    # 找到了
                    if find_evaluator(info_dict, ffn) is False:
                        continue
                    part_name = info_dict.get(
                        cls.__PART_NAME_LITERAL)
                    subsystem_name = info_dict.get(
                        cls.__SUBSYSTEM_NAME_LITERAL)
                    info_dict[cls.__SIZE_LITERAL] = size
                    cls.__put(subsystem_name, part_name, info_dict)
                    found_flag = True
                    break
                if found_flag:
                    break
            if found_flag is False:
                cls.__put(cls.__OTHERS_LITERAL,
                          cls.__OTHERS_LITERAL, {
                              "output_path": ffn,
                              "size": size
                          })

    @classmethod
    def analysis(cls, project_path: str, out_system_dir: str, update_gn_info=False):
        """
        正式分析的脚本
        project_path：openharmony项目根路径
        out_system_dir：out目录下到system目录的路径
        """
        exe_dir_list = [os.path.join(out_system_dir, "bin"), ]
        so_dir_list = [os.path.join(out_system_dir, "lib"), os.path.join(
            out_system_dir, "lib64")]
        cls.__rom_info_dict[cls.__SIZE_LITERAL] = 0
        etc_dir_list = list()
        for x in os.listdir(out_system_dir):
            if x not in ("bin", "lib", "lib64", "profile"):
                etc_dir_list.append(os.path.join(out_system_dir, x))
        profile_dir_list = [os.path.join(out_system_dir, "profile")]
        out_file = "rom_analysis.json"
        gn_info_dict = dict()
        exe_full_path = list()
        so_full_path = list()
        etc_full_path = list()
        profile_full_path = list()
        with ThreadPoolExecutor(max_workers=5) as pool:
            gn_info_dict_getter = pool.submit(
                GnInfoCollector.collect_gn_info, project_path, update_gn_info)

            exe_name_getter = pool.submit(cls.find_abs_files_of_list,
                                          exe_dir_list, TargetFileFilterBox.exe_filter)
            so_name_getter = pool.submit(cls.find_abs_files_of_list,
                                         so_dir_list, TargetFileFilterBox.so_filter)
            etc_name_getter = pool.submit(cls.find_abs_files_of_list,
                                          etc_dir_list, TargetFileFilterBox.etc_filter)
            profile_name_getter = pool.submit(cls.find_abs_files_of_list,
                                              profile_dir_list)
            gn_info_dict = gn_info_dict_getter.result()
            exe_full_path = exe_name_getter.result()
            so_full_path = so_name_getter.result()
            etc_full_path = etc_name_getter.result()
            profile_full_path = profile_name_getter.result()
        # 用这些key去gn_info_dict中查找列表
        exe_keys = ("ohos_executable", "executable")
        so_keys = ("ohos_shared_library", "shared_library")
        etc_keys = ("ohos_prebuilt_etc",)
        profile_keys = ("ohos_sa_profile",)
        with ThreadPoolExecutor(max_workers=4) as pool:
            t1 = pool.submit(cls.__analysis_template, exe_keys,
                             exe_full_path, gn_info_dict, FindEvaluatorBox.exe_evaluator)
            t2 = pool.submit(cls.__analysis_template, so_keys,
                             so_full_path, gn_info_dict, FindEvaluatorBox.so_evaluator)

            t3 = pool.submit(cls.__analysis_template, etc_keys,
                             etc_full_path, gn_info_dict, FindEvaluatorBox.etc_evaluator)
            t4 = pool.submit(cls.__analysis_template, profile_keys, profile_full_path,
                             gn_info_dict, FindEvaluatorBox.etc_evaluator)
            t1.result()
            t2.result()
            t3.result()
            t4.result()
        # cls.__analysis_template(so_keys, so_full_path, gn_info_dict, FindEvaluatorBox.so_evaluator)
        with open(out_file, 'w', encoding='utf-8') as f:
            f.write(json.dumps(cls.__rom_info_dict, indent=4))


def get_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("-p", "--project_path", default=".",
                        type=str, help="root path of openharmony")
    parser.add_argument("-t", "--target_path", default=".",
                        type=str, help="path of target resource")
    args = parser.parse_args()
    return args


if __name__ == "__main__":
    # args = get_args()
    # project_path = os.path.abspath(args.project_path)
    # target_path = os.path.abspath(args.target_path)
    project_path = "/home/aodongbiao/rkmini"
    target_path = "/home/aodongbiao/rkmini/out/rk3568/packages/phone/system"
    logging.getLogger().setLevel(logging.ERROR)
    RomAnalysisTool.analysis(project_path, target_path)
